﻿using System;
using System.Runtime.InteropServices;
using WhiteMagic;
using WhiteMagic.Internals;

namespace WardenMonitor
{

    public class PacketHandler
    {
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate uint WowPacketHandler(uint zero, uint opcode, uint timestamp, ref DataStore dataStore);

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void WowSendPacket(IntPtr buffer, int bufferLength);

        private static readonly Magic _magic = new Magic();
        private bool _firstOutgoingPacket = true;
        private byte[] _outgoingKeyState;
        private Detour _detourIncoming;
        private Detour _detourOutgoing;

        public event EventHandler<PacketEvent> OnIncomingPacket;
        public event EventHandler<PacketEvent> OnOutgoingPacket;

        public void DetourHandler()
        {
            _detourIncoming =
                _magic.Detours.CreateAndApply(_magic.RegisterDelegate<WowPacketHandler>(Warden.MessageHandler),
                                              new WowPacketHandler(HandleIncomingPacket), "IncomingPacket");
            _detourOutgoing =
                _magic.Detours.CreateAndApply(_magic.RegisterDelegate<WowSendPacket>(Warden.SendPacket),
                                              new WowSendPacket(HandleOutgoingPacket), "OutgoingPacket");
        }

        private void HandleOutgoingPacket(IntPtr buffer, int bufferLength)
        {
            // first packet: our key state is transformed and useless for this data and we don't have the previous one
            // save it so we can decrypt all following packets
            if (_firstOutgoingPacket)
            {
                _outgoingKeyState = Warden.GetKeyOut();
                _firstOutgoingPacket = false;
                return;
            }

            var data = _magic.ReadBytes(buffer, bufferLength);
            var decrypted = RC4.Crypt(_outgoingKeyState, data);
            _outgoingKeyState = Warden.GetKeyOut();

            Log.WriteLine(DateTime.Now + " [CMSG_WARDEN_DATA]");
            Log.WriteLine(Utility.HexDump(decrypted));
            Log.WriteLine();

            if (OnOutgoingPacket != null)
                OnOutgoingPacket(this, new PacketEvent(new Packet(PacketDirection.Outgoing, decrypted)));

            _detourOutgoing.CallOriginal(new object[] {buffer, bufferLength});
        }

        private uint HandleIncomingPacket(uint zero, uint opcode, uint timestamp, ref DataStore dataStore)
        {
            var remaining = (int)(dataStore.Size - dataStore.Read);
            var data = _magic.ReadBytes(new IntPtr(dataStore.Buffer.ToInt32() + dataStore.Base + dataStore.Read),
                                        remaining);
            var key = Warden.GetKeyIn();
            var decrypted = RC4.Crypt(key, data);

            Log.WriteLine(DateTime.Now + " [SMSG_WARDEN_DATA]");
            Log.WriteLine(Utility.HexDump(decrypted));
            Log.WriteLine();

            if (OnIncomingPacket != null)
                OnIncomingPacket(this, new PacketEvent(new Packet(PacketDirection.Incoming, decrypted)));

            return (uint)_detourIncoming.CallOriginal(new object[] {zero, opcode, timestamp, dataStore});
        }
    }

}